 /* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1/commercial
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may use this file in compliance with the License.
 * Alternatively, if you can't copy with the License, namely if you do not
 * want to provide source code of your modifications to this file, you may
 * negotiate a commercial licence with the initial developer.
 * You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is COID.
 *
 * The Initial Developer of the Original Code is
 * PosAm.
 * Portions created by the Initial Developer are Copyright (C) 2003
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 * Brano Kemen
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#include "serverface.h"
#include "server.h"
#include "acceptor.h"
#include "threadmgr.h"

#include "coid/comm/dir.h"
#include "coid/comm/binstream/textstream.h"
#include "coid/comm/pthreadx.h"

#include <algorithm>


namespace coid {

////////////////////////////////////////////////////////////////////////////////
#define CREATE_HOOK(M) \
    *__vtbl.add(1) = (txx_func)&CoidNode::M

////////////////////////////////////////////////////////////////////////////////
cmd_interface_vtbl* ServiceInstanceCoid::get_cmdin()
{
    typedef void (CoidNode::*txx_func) (void);

    static dynarray<txx_func>  __vtbl;

    if( __vtbl.size() > 0 )
        return (cmd_interface_vtbl*)&__vtbl[0];

    // add new methods always to the end
    // remove methods by setting the entry to a exception thrower
    CREATE_HOOK(mutex_wrlock);
    CREATE_HOOK(mutex_rdlock);
    CREATE_HOOK(mutex_unlock);
    CREATE_HOOK(set_msg);
    CREATE_HOOK(get_id);

    CREATE_HOOK(short_name);
    CREATE_HOOK(class_name);
    CREATE_HOOK(get_name);
    CREATE_HOOK(set_name);

    CREATE_HOOK(set_option);
    CREATE_HOOK(get_option);

    CREATE_HOOK(set_method_flags);

    //CREATE_HOOK(attach_service);
    CREATE_HOOK(dispatch_method);

    CREATE_HOOK(spawn_thread);
    CREATE_HOOK(free_threads);

    CREATE_HOOK(parse_path);
    CREATE_HOOK(get_server_address);

    CREATE_HOOK(get_superior_id);

    CREATE_HOOK(list_devices);
    CREATE_HOOK(list_conn_devices);

    CREATE_HOOK(get_device_info);
    CREATE_HOOK(get_working_dir);
    CREATE_HOOK(find_nodes_of_class);
    CREATE_HOOK(get_conn_info);

    CREATE_HOOK(get_object_address);
    CREATE_HOOK(get_server_address_nl);

    CREATE_HOOK(get_method_desc);

    CREATE_HOOK(set_identity);

    CREATE_HOOK(is_exit_signalled);

    CREATE_HOOK(open_service);

    CREATE_HOOK(get_connection_id);

    CREATE_HOOK(request_autodestroy);

    RASSERTX( __vtbl.size() * sizeof(txx_func) == sizeof(cmd_interface_vtbl), "cmdin virtual table disintegrated" );

    return (cmd_interface_vtbl*)&__vtbl[0];
}

////////////////////////////////////////////////////////////////////////////////
///Command interface implementation

CoidNode::~CoidNode()
{
//#ifdef _DEBUG
//	if( SRVROOTA._objmgr.is_valid((LNID) _id) )
//		DASSERT( 0 );
//#endif

	_mutex.disable();
    //free_threads();
/*
    ServerGlobal::REF id;
    if (SRVROOTA.get_superior (_id, id)) {
        if (id->_class == ConnectionCoid::get_node_class ())
        {
            ConnectionCoid* con = (ConnectionCoid*)id->_node;
            con->unlink (SRVROOTA.get_node (_id));
        }
    }
*/

//#ifdef _DEBUG
//	if( SRVROOTA._objmgr.is_valid((LNID) _id) )
//		DASSERT( 0 );
//#endif
}

////////////////////////////////////////////////////////////////////////////////
CoidNode::CoidNode( uint id )
{
    _timetodie = 0;
    _id = id;
    _flags = 0;
	_mutex.set_name( "CoidNode" );
	_mutex.set_objid( _id );
}

////////////////////////////////////////////////////////////////////////////////
void CoidNode::mutex_wrlock()         { _mutex.wr_lock(); }
void CoidNode::mutex_rdlock() const   { _mutex.rd_lock(); }

void CoidNode::mutex_unlock() const   { _mutex.unlock(); }

////////////////////////////////////////////////////////////////////////////////
token CoidNode::short_name() const
{
    return GET_VIRTUAL_INTERFACE().get_class_name();
}

////////////////////////////////////////////////////////////////////////////////
opcd CoidNode::get_name( charstr& name ) const
{
    token t;
    opcd e = SRVROOTA._objmgr.get_node_name( _id, t );
    if(!e)  name = t;
    return e;
}

////////////////////////////////////////////////////////////////////////////////
opcd CoidNode::set_name( const token& name )
{
    return SRVROOTA._objmgr.set_node_name( _id, name );
}

////////////////////////////////////////////////////////////////////////////////
token CoidNode::object_name() const
{
    token t;
    if( 0 == SRVROOTA._objmgr.get_node_name( _id, t ) )
        return t;
    t.set_empty();
    return t;
}

////////////////////////////////////////////////////////////////////////////////
opcd CoidNode::object_name( const token& n )
{
    return SRVROOTA._objmgr.set_node_name( _id, n );
}

////////////////////////////////////////////////////////////////////////////////
opcd CoidNode::set_option( uint optid, int value )
{
    NodeClass nc = GET_VIRTUAL_INTERFACE();
    if( !nc.is_of_type( ServiceInstanceCoid::GET_INTERFACE() ) )
        return ersUNAVAILABLE "operation can be performed only on ServiceInstanceCoid objects";

    switch(optid)
    {
    case cmd_interface::OPTION_ALLOW_SHARED_ACCESS:
        {
            if( value )
                ((ServiceInstanceCoid*)this)->_flags |= ServiceInstanceCoid::fALLOW_SHARED;
            else
                ((ServiceInstanceCoid*)this)->_flags &= ~ServiceInstanceCoid::fALLOW_SHARED;
            return 0;
        }

    case cmd_interface::OPTION_LOG_CALLS:
        {
            if(value) {
                ((ServiceInstanceCoid*)this)->_flags |= ServiceInstanceCoid::fLOG_CALL;
                if( get_verbosity() < 3 )  set_verbosity(3);
            }
            else
                ((ServiceInstanceCoid*)this)->_flags &= ~ServiceInstanceCoid::fLOG_CALL;
            return 0;
        }
    }
    
    return ersINVALID_PARAMS "unknown option";
}

////////////////////////////////////////////////////////////////////////////////
opcd CoidNode::get_option( uint optid, int* value ) const
{
    NodeClass nc = GET_VIRTUAL_INTERFACE();
    if( !nc.is_of_type( ServiceInstanceCoid::GET_INTERFACE() ) )
        return ersUNAVAILABLE "operation can be performed only on ServiceInstanceCoid objects";

    switch(optid)
    {
    case cmd_interface::OPTION_ALLOW_SHARED_ACCESS:
        {
            *value = (((ServiceInstanceCoid*)this)->_flags & ServiceInstanceCoid::fALLOW_SHARED) != 0;
            return 0;
        }

    case cmd_interface::OPTION_LOG_CALLS:
        {
            *value = (((ServiceInstanceCoid*)this)->_flags & ServiceInstanceCoid::fLOG_CALL) != 0;
            return 0;
        }
    }

    return ersINVALID_PARAMS "unknown option";
}

////////////////////////////////////////////////////////////////////////////////
binstream& CoidNode::set_msg( uchar msgclass, uchar verb ) const
{
    if( !verbosity_level_ok(verb) )
        return nullstream;

    ConnectionCoid* conn = ConnectionCoid::get_active_conn();
    if( SINGLETON(ServerGlobal).being_destroyed() )
        return nullstream;

    return *SRVROOTA._log.get_entry( _id, msgclass, verb, short_name(), conn ? conn->_nick.ptr() : 0 );
}

////////////////////////////////////////////////////////////////////////////////
opcd CoidNode::set_method_flags (uint obj, uint method, uint flags)
{
    NODEREF<CoidNode> n;
    opcd e = SRVROOTA._objmgr.get_node( obj, n );
    if(!e)
        e = ersNOT_IMPLEMENTED;

    return e;
    //return id->_node->dpx_modify_method_flags( method, flags );
}

////////////////////////////////////////////////////////////////////////////////
uint CoidNode::get_id() const
{
    return _id;
}

uint CoidNode::get_connection_id() const
{
    ConnectionCoid* conn = ConnectionCoid::get_active_conn();
    if(!conn)
        return UMAX32;

    return conn->get_id();
}
/*
////////////////////////////////////////////////////////////////////////////////
opcd CoidNode::get_threads_status( charstr& out )
{
    for (uint i=0; i<_threads.size (); ++i)
    {
        if (_threads[i]  &&  _threads[i]->_tid)
        {
#ifdef SYSTYPE_MSVC
            HANDLE h = pthread_getw32threadhandle_np( _threads[i]->_tid );

            FILETIME tcr, tex, tkr, tus;
            if (GetThreadTimes( h, &tcr, &tex, &tkr, &tus ))
            {
                __int64 k, u;
                k = tkr.dwHighDateTime;  k <<= 32;
                k += tkr.dwLowDateTime;
                u = tus.dwHighDateTime;  u <<= 32;
                u += tus.dwLowDateTime;
                //out << "thread \"" << _threads[i]->_name << "\" used " <<
            }
#endif
        }
    }
    return 0;
}
*/
////////////////////////////////////////////////////////////////////////////////
opcd CoidNode::free_threads()
{
    return threads_free();
}

////////////////////////////////////////////////////////////////////////////////
/*
opcd CoidNode::attach_service(
    const token& service,
    const version& ver,
    LNID& oid,
    binstream* arg
    )
{
    ConnectionCoid* conn = SRVROOTA.get_active_conn();
    if (!conn)  return ersUNAVAILABLE "not under a connection";

    uint n = conn->find_svci(_id);
    uint sid;
    opcd e = conn->attach_svci( &sid, service, ver, n != UMAX  ?  (LNID)_id  :  UMAX );

    if(!e)
        oid = conn->_inst[sid]->get_id();

    return e;
}
*/

////////////////////////////////////////////////////////////////////////////////
opcd CoidNode::spawn_thread( t_thread_fnc start, void* arg, bool crit, const token& name )
{
    return SRVROOTA._objmgr.spawn_thread( _id, start, arg, crit, name );
}

////////////////////////////////////////////////////////////////////////////////
opcd CoidNode::list_devices( const token& classname, const version& ver, const token& name, dynarray<uint>& devices ) const
{
    //NodeClass pit = INTERFACE_REGISTER(ttree_ifc).find_best( classname, ver );
    //if( !pit.is_set() )
    //    return ersNOT_FOUND "class name or version invalid";

    return SRVROOTA._objmgr.find_nodes_of_class( 0, classname, &ver, name, devices );
}

////////////////////////////////////////////////////////////////////////////////
opcd CoidNode::list_conn_devices( const token& classname, const version& ver, const token& name, dynarray<uint>& devices ) const
{
    ConnectionCoid* conn = ConnectionCoid::get_active_conn();
    if(!conn)
        return ersUNAVAILABLE "not under a connection";
    //GCOID_LOCK(_id,"get_conn_device");

    //NodeClass pit = INTERFACE_REGISTER(ttree_ifc).find(classname,ver);
    //if( pit == 0 )
    //    return ersNOT_FOUND "class name or version invalid";

    uints ni = conn->_inst.size();
    for( uints i=0; i<ni; ++i )
    {
        if( conn->_inst[i] )
        {
            NODEREF<ServiceInstanceCoid> refid;
            if( 0 != SRVROOTA._objmgr.get_node_of_class( conn->_inst[i]->get_id(), name, &ver, refid ) )
                continue;

            if( ! classname.is_empty()  &&  refid->_desc->_name != classname )
                continue;

            *devices.add(1) = conn->_inst[i]->get_id();
        }
    }
    return 0;
}

////////////////////////////////////////////////////////////////////////////////
opcd CoidNode::get_conn_info( charstr& out ) const
{
    ConnectionCoid* conn = ConnectionCoid::get_active_conn();
    if(!conn)
        return ersUNAVAILABLE "not under a connection";

    conn->get_client_addr(out);
    return 0;
}

////////////////////////////////////////////////////////////////////////////////
opcd CoidNode::parse_path( uint id, const token& tok, dynarray<uint>& out ) const
{
    return SRVROOTA.parse_path( id, _id, tok, out );
}

////////////////////////////////////////////////////////////////////////////////
charstr& CoidNode::get_server_address( charstr& buf ) const
{
    buf = SRVROOTA.get_coidaccpstring();
    return buf;
}

////////////////////////////////////////////////////////////////////////////////
netAddress* CoidNode::get_server_address_nl (netAddress* buf) const
{
    *buf = SRVROOTA.get_coidaccp();
    return buf;
}

////////////////////////////////////////////////////////////////////////////////
charstr& CoidNode::get_working_dir( charstr& buf ) const
{
    token t = _path;
    buf.set_left_back( _path, directory::separator(), true );
    buf.append( directory::separator() );
    return buf;
}

////////////////////////////////////////////////////////////////////////////////
uint CoidNode::get_superior_id() const
{
    NODEREF<CoidNode> ids;
    if( 0 == SRVROOTA._objmgr.get_superior( _id, ids ) )
        return ids->get_id();
    return UMAX32;
}

////////////////////////////////////////////////////////////////////////////////
opcd CoidNode::get_device_info( uint id, charstr& service, version& ver, charstr& name ) const
{
    NODEREF<CoidNode> node;
    opcd e = SRVROOTA._objmgr.get_node( id, node );
    if(!e)
    {
        service = node.class_name();
        ver = node.get_version();
        name = node.name();
    }
    return e;
}

////////////////////////////////////////////////////////////////////////////////
opcd CoidNode::find_nodes_of_class( uint from, const charstr& name, const token& classname, const version& ver, dynarray<uint>& lst ) const
{
    return SRVROOTA._objmgr.find_nodes_of_class( from, classname, &ver, name, lst );
}

////////////////////////////////////////////////////////////////////////////////
opcd CoidNode::get_object_address( netaddr& addr, uint* obj ) const
{
    netAddress ad = SRVROOTA.get_coidaccp();

    NetAddress2netaddr( &ad, &addr );
    *obj = _id;
    return 0;
}

////////////////////////////////////////////////////////////////////////////////
opcd CoidNode::get_method_desc( uint obj, const ServiceDescriptor::MethodDesc** pdesc, uint* nmeth ) const
{
    //handle ServiceInstanceCoid type .. 
    NODEREF<ServiceInstanceCoid> svci;
    opcd e = SRVROOTA._objmgr.get_node( obj, svci );
    if(!e)
    {
        *pdesc = svci->_desc->_methflags;
        *nmeth = svci->_desc->_nmeth;
        return e;
    }

    //.. or ServiceCoid type
    NODEREF<ServiceCoid> svc;
    e = SRVROOTA._objmgr.get_node( obj, svc );
    if(!e)
    {
        *pdesc = svc->_desc._methflags;
        *nmeth = svc->_desc._nmeth;
    }

    return e;
}

////////////////////////////////////////////////////////////////////////////////
opcd CoidNode::set_identity( account_id& acid, bool create )
{
    opcd e;
    AccountMgr_client& accmgr = SRVROOTA._accmgr;

    if( !accmgr.is_connected() )
    {
        sysMilliSecondSleep(200);
        return ersFE_DENIED "accmgr not ready";
    }

    account acc;
    (account_id&)acc = acid;

    e = accmgr.find_account(acc);
    if( create && e == ersINVALID_LOGIN_NAME )
        e = accmgr.add_account(acc,false);

    if(e)
        return e;

    acid._uniqid = acc._uniqid;
    return 0;
}

////////////////////////////////////////////////////////////////////////////////
opcd CoidNode::open_service( COID_OPEN_SERVICE& cos )
{
    NodeClass nc;
    opcd e = SRVROOTA._objmgr.get_node_class( _id, nc );
    if(e)  return e;
    if( !nc.is_of_type( ServiceInstanceCoid::GET_INTERFACE() ) )
        return ersUNAVAILABLE "invalid operation";

    uint mode = cos.get_conn_mode();
    ConnectionCoid* conp = 0;
    NODEREF<ConnectionCoid> conn;
    if( !(mode & StdProtocolMethod::ConnectFlags::fACCESS_MODE_DIRECT) )
    {
        conp = ConnectionCoid::get_active_conn();
        if(!conp)
            return ersINVALID_PARAMS "invalid access mode - no connection";

        e = SRVROOTA._objmgr.get_node( conp->get_id(), conn );
        if(e)
            return ersINTERNAL_ERROR "mismatched connection object information";
    }

    NODEREF<CoidAcceptorCoid> coidaccp;

    e = SRVROOTA._objmgr.get_node( SRVROOTA._accpcoid_nodeid, coidaccp );
    if(e)
        return e;

    netAddress addr;
    if(conp)
        conp->get_client_addr(addr);
	else
		addr = SRVROOTA.get_coidaccp();

	inoutstream bio( &cos.bw, &cos.br );
    e = coidaccp->attach_to_connection( conn, bio, addr );
	return e;
}

////////////////////////////////////////////////////////////////////////////////
opcd CoidNode::request_autodestroy( uint secfwd )
{
    time_t tt;
    time(&tt);
    _timetodie = tt + secfwd;

    SRVROOTA.register_autodestroy(_id);

    return 0;
}


} // namespace coid
